#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <malloc.h>

#include "fmap.h"
#include "treefile.h"

#pragma pack(1)
typedef struct _nodeheader_t {
	uint64_t end_offset;
	uint16_t enter_blob_size;
	uint16_t leave_blob_size;
} nodeheader_t;
#pragma pack()


TREEFILE_STATIC int treefile_init(treefile_t * self, int fd, int max_depth)
{
	RETURN_WITH_CODE_IF(TREEFILE_ERR_INVALID_MAX_DEPTH, max_depth <= 0);

	self->fd = fd;
	self->max_depth = max_depth;
	self->curr_level = 0;
	self->offsets_stack = malloc(sizeof(off_t) * max_depth);
	if (self->offsets_stack == NULL)
		goto cleanup1;
	if (fwindow_init(&self->head, fd, 1024*1024) != 0)
		goto cleanup2;
	if (fmap_init(&self->jumper, fd, FMAP_READ | FMAP_WRITE, 1024*1024, 512*1024) != 0)
		goto cleanup3;

	return 0;

cleanup3:
	fwindow_fini(&self->head);
cleanup2:
	free(self->offsets_stack);
cleanup1:
	self->offsets_stack = NULL;
	return -1;
}

TREEFILE_STATIC inline int treefile_leave(treefile_t * self,
		const void * extra_payload, uint16_t extra_payload_size);

TREEFILE_STATIC int treefile_fini(treefile_t * self)
{
	int res = 0;

	// close all open nodes
	while (self->curr_level > 0) {
		treefile_leave(self, NULL, 0);
	}

	self->fd = -1;
	if (self->offsets_stack != NULL) {
		free(self->offsets_stack);
		self->offsets_stack = NULL;
	}
	if (fwindow_fini(&self->head) != 0) {
		res = -1;
	}
	if (fmap_fini(&self->jumper) != 0) {
		res = -1;
	}
	return res;
}

TREEFILE_STATIC inline int treefile_enter(treefile_t * self,
		const void * payload, uint16_t payload_size)
{
	nodeheader_t header = {0xffffffffffffffffLL, payload_size, 0};

	// is it too deep?
	RETURN_WITH_CODE_IF(TREEFILE_ERR_TOO_DEEP, self->curr_level > self->max_depth);

	// remember where we write the header
	self->offsets_stack[self->curr_level] = fwindow_tell(&self->head);

	if (fwindow_write(&self->head, &header, sizeof(header)) != 0) {
		return -1;
	}
	if (fwindow_write(&self->head, payload, payload_size) != 0) {
		return -1;
	}

	self->curr_level += 1;
	return 0;
}

TREEFILE_STATIC inline int treefile_leave(treefile_t * self,
		const void * extra_payload, uint16_t extra_payload_size)
{
	nodeheader_t * header;
	off_t header_offset;

	// make sure we don't jump above the root
	RETURN_WITH_CODE_IF(TREEFILE_ERR_TOO_SHALLOW, self->curr_level < 1);

	header_offset = self->offsets_stack[self->curr_level - 1];
	header = fmap_map(&self->jumper, header_offset, sizeof(header_offset));
	if (header == NULL) {
		return -1;
	}

	header->end_offset = fwindow_tell(&self->head);
	header->leave_blob_size = extra_payload_size;

	if (fwindow_write(&self->head, extra_payload, extra_payload_size) != 0) {
		return -1;
	}

	self->curr_level -= 1;
	return 0;
}

TREEFILE_STATIC int treefile_open(treefile_t * self, const char * filename, int max_depth)
{
	int ret = 0;
	int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC,
			S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
	RETURN_WITH_CODE_IF(TREEFILE_ERR_OPEN_FAILED, fd < 0);
	ret = treefile_init(self, fd, max_depth);
	if (ret != 0) {
		close(fd);
		self->fd = -1;
	}
	return ret;
}

TREEFILE_STATIC int treefile_close(treefile_t * self)
{
	int ret;
	ret = treefile_fini(self);
	if (self->fd >= 0) {
		close(self->fd);
		self->fd = -1;
	}
	return ret;
}


/*
#define MAKESURE(x) if ((x) != 0) {printf("%s failed\n", #x); return 1;}

int main()
{
	treefile_t tf;
	MAKESURE(treefile_open(&tf, "/tmp/bar", 100));
	MAKESURE(treefile_enter(&tf, "hello", 5, 5));
	MAKESURE(treefile_enter(&tf, "bazug1", 6, 6));
	MAKESURE(treefile_leave(&tf, "guzab1"));
	MAKESURE(treefile_enter(&tf, "bazug2", 6, 6));
	MAKESURE(treefile_leave(&tf, "guzab2"));
	MAKESURE(treefile_leave(&tf, "world"));
	MAKESURE(treefile_close(&tf));

	printf("success\n");
	return 0;
}

*/



